using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using RideMe.Core;
using System.IO;
using TD.SandDock;
using ICSharpCode.TextEditor;
using ICSharpCode.TextEditor.Document;
using RideMe.Presentation.Properties;
using System.Threading;
using RideMe.Core.Parser;
using ICSharpCode.TextEditor.Actions;

namespace RideMe.Presentation.Controls
{
    public partial class DocumentExplorer : UserControl
    {
        private const int WindowBorderOffset = 1;
        private readonly string ClassMethodDropDownControlName = "classMethodDropDowns";
        private readonly string TextEditorControlName = "textEditor";

        public DocumentExplorer()
        {
            InitializeComponent();
            HookProjectServiceEvents();
            Options.Instance.OptionsChanged += new EventHandler(options_OptionsChanged);
            ActiveDocumentInformation.Instance.TabSwitched += new EventHandler<SwitchTabEventArgs>(Instance_TabSwitched);

            // Set to make the tabs show up a little smoother.
            this.DoubleBuffered = true;

            //StartParsingThread();
            RubyParser.ParseComplete += new EventHandler(RubyParser_ParseComplete);
        }

        /// <summary>
        /// Hooks the project service events.
        /// </summary>
        private void HookProjectServiceEvents()
        {
            ProjectService.FileOpened += new EventHandler<ProjectFileEventArgs>(ProjectService_FileOpened);
            ProjectService.ProjectClosed += new EventHandler(ProjectService_ProjectClosed);
            ProjectService.SaveClicked += new EventHandler(ProjectService_SaveClicked);
            ProjectService.SaveAllClicked += new EventHandler(ProjectService_SaveAllClicked);
            ProjectService.SaveAsClicked += new EventHandler<FileRenameEventArgs>(ProjectService_SaveAsClicked);
            ProjectService.OpenFileSelected += new EventHandler<ProjectFileEventArgs>(ProjectService_OpenFileSelected);
            ProjectService.FileRenamed += new EventHandler<FileRenameEventArgs>(ProjectService_FileRenamed);
            ProjectService.FileDeleted += new EventHandler<ProjectFileEventArgs>(ProjectService_FileDeleted);
            ProjectService.PreparedForServer += new EventHandler<ProjectFileEventArgs>(ProjectService_PreparedForServer);
        }

        /// <summary>
        /// Unhooks the project service events.
        /// </summary>
        private void UnhookProjectServiceEvents()
        {
            ProjectService.FileOpened -= new EventHandler<ProjectFileEventArgs>(ProjectService_FileOpened);
            ProjectService.ProjectClosed -= new EventHandler(ProjectService_ProjectClosed);
            ProjectService.SaveClicked -= new EventHandler(ProjectService_SaveClicked);
            ProjectService.SaveAllClicked -= new EventHandler(ProjectService_SaveAllClicked);
            ProjectService.SaveAsClicked -= new EventHandler<FileRenameEventArgs>(ProjectService_SaveAsClicked);
            ProjectService.OpenFileSelected -= new EventHandler<ProjectFileEventArgs>(ProjectService_OpenFileSelected);
            ProjectService.FileRenamed -= new EventHandler<FileRenameEventArgs>(ProjectService_FileRenamed);
            ProjectService.FileDeleted -= new EventHandler<ProjectFileEventArgs>(ProjectService_FileDeleted);
            ProjectService.PreparedForServer -= new EventHandler<ProjectFileEventArgs>(ProjectService_PreparedForServer);
        }

        /// <summary>
        /// Used to signify whether the document 
        /// is being loaded for the first time.
        /// If so, this will stop the tab from
        /// being set to dirty.
        /// </summary>
        private bool _loadingDocument;

        private ContextMenuStrip _contextMenu;

        /// <summary>
        /// Gets or sets the <see cref="T:System.Windows.Forms.ContextMenuStrip"></see> associated with this control.
        /// </summary>
        /// <value></value>
        /// <returns>The <see cref="T:System.Windows.Forms.ContextMenuStrip"></see> for this control, or null if there is no <see cref="T:System.Windows.Forms.ContextMenuStrip"></see>. The default is null.</returns>
        public override ContextMenuStrip ContextMenuStrip
        {
            get { return _contextMenu; }
            set { _contextMenu = value; }
        }

        /// <summary>
        /// Gets the active text editor.
        /// </summary>
        /// <value>The active text editor.</value>
        public TextEditorControl ActiveTextEditor
        {
            get 
            {
                if (documentContainer.ActiveDocument == null) return null;
                //if (documentContainer.ActiveDocument.Controls.Count == 1) return null;

                return documentContainer.ActiveDocument.Controls[TextEditorControlName] as TextEditorControl;
            }
        }

		public bool CanCloseActiveWindow
		{
			get
			{
				DockControl window = documentContainer.ActiveDocument;
				return (window != null && window.Closable);
			}
		}

		public bool CanCloseAll
		{
			get
			{
				return documentContainer.Documents.Length > 0;
			}
		}

		public void CloseWindow()
		{
			DockControl window = documentContainer.ActiveDocument;
            if ((window != null) && (window.Closable))
            {
                window.Close();
            }
		}

		public void CloseAll()
		{
			foreach (DockControl window in documentContainer.Documents)
			{
                if (window.Closable)
                {
                    window.Close();
                }
			}
		}

		public void CloseAllButCurrent()
		{
			foreach (DockControl window in documentContainer.Documents)
			{
                if ((window.Closable) && (window != documentContainer.ActiveDocument))
                {
                    window.Close();
                }
			}
		}

        /// <summary>
        /// Gets a value indicating whether this instance can undo.
        /// </summary>
        /// <value><c>true</c> if this instance can undo; otherwise, <c>false</c>.</value>
        public bool CanUndo
        {
            get 
            {
                bool canUndo = false;
				if (ActiveTextEditorExists)
                {
                    canUndo = ActiveTextEditor.Document.UndoStack.CanUndo;
                }

                return canUndo;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance can redo.
        /// </summary>
        /// <value><c>true</c> if this instance can redo; otherwise, <c>false</c>.</value>
        public bool CanRedo
        {
            get 
            {
                bool canRedo = false;
				if (ActiveTextEditorExists)
                {
                    canRedo = ActiveTextEditor.Document.UndoStack.CanRedo;
                }

                return canRedo;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance can cut copy paste.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance can cut copy paste; otherwise, <c>false</c>.
        /// </value>
        public bool CanCutCopyPaste
        {
            get
            {
				return ActiveTextEditorExists;
            }
        }

        /// <summary>
        /// Gets a value indicating whether [active editor dirty].
        /// </summary>
        /// <value><c>true</c> if [active editor dirty]; otherwise, <c>false</c>.</value>
        public bool ActiveEditorDirty
        {
            get
            {
                string filePath = documentContainer.ActiveDocument.Tag.ToString();

				if (!ActiveTextEditorExists) return false;
                if (!File.Exists(filePath)) return false;

                return IsEditorDirty(documentContainer.ActiveDocument);
            }
        }

		/// <summary>
		/// Gets a value indicating whether there is an active editor.
		/// </summary>
		/// <value><c>true</c> if [active editor dirty]; otherwise, <c>false</c>.</value>
		public bool ActiveTextEditorExists
		{
			get
			{
				return ActiveTextEditor != null;
			}
		}

        /// <summary>
        /// Gets the active file path.
        /// </summary>
        /// <value>The active file path.</value>
        public string ActiveFilePath
        {
            get
            {
				if (!ActiveTextEditorExists) return string.Empty;

                return documentContainer.ActiveDocument.Tag.ToString();
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is something selected.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is something selected; otherwise, <c>false</c>.
        /// </value>
        public bool IsSomethingSelected
        {
            get
            {
				if (!ActiveTextEditorExists) return false;

                return ActiveTextEditor.ActiveTextAreaControl.TextArea.SelectionManager.HasSomethingSelected;
            }
        }

        /// <summary>
        /// Gets a value indicating whether [method selected].
        /// </summary>
        /// <value><c>true</c> if [method selected]; otherwise, <c>false</c>.</value>
        public bool MethodSelected
        {
            get
            {
                LineSegment activeLine = ActiveTextEditor.ActiveTextAreaControl.TextArea.Document.GetLineSegment(ActiveTextEditor.ActiveTextAreaControl.TextArea.Caret.Line);

                if (activeLine.Words == null) return false;

                foreach (TextWord word in activeLine.Words)
                {
                    if (word.Word == Resources.RubyMethodKeyword)
                    {
                        return true;
                    }
                }

                return false;
            }
        }

        public bool RubyFileActive
        {
            get { return FileSystemService.IsRubyFile(ActiveFilePath); }
        }

        /// <summary>
        /// Gets the name of the selected method.
        /// </summary>
        /// <value>The name of the selected method.</value>
        public string SelectedMethodName
        {
            get
            {
                if (!MethodSelected) return string.Empty;

                return GetSelectedMethodName(); 
            }
        }

        /// <summary>
        /// Gets the name of the selected method.
        /// </summary>
        /// <returns></returns>
        private string GetSelectedMethodName()
        {
            LineSegment activeLine = ActiveTextEditor.ActiveTextAreaControl.TextArea.Document.GetLineSegment(ActiveTextEditor.ActiveTextAreaControl.TextArea.Caret.Line);

            return activeLine.Words[activeLine.Words.Count - 1].Word;
        }

        /// <summary>
        /// Determines whether [is editor dirty] [the specified editor].
        /// </summary>
        /// <param name="editor">The editor.</param>
        /// <param name="filePath">The file path.</param>
        /// <returns>
        /// 	<c>true</c> if [is editor dirty] [the specified editor]; otherwise, <c>false</c>.
        /// </returns>
        private bool IsEditorDirty(DockControl document)
        {
            if (document == null) return false;
            return document.Text.Contains(Resources.Asterisk);
        }

        /// <summary>
        /// Checks all editors.
        /// </summary>
        /// <returns></returns>
        public bool CheckAllEditors()
        {
            // TODO: Refactor -- exit and AskToSaveChanges names are ambiguous as hell.
            // Think about using a cancel out param for clarity.
            bool exit = true;
            
            foreach (DockControl control in documentContainer.Controls)
            {
                TextEditorControl editor = control.Controls[TextEditorControlName] as TextEditorControl;

                if (editor == null) break;

                if (IsEditorDirty(control))
                {
                    exit = AskToSaveChanges(control.Tag.ToString(), editor.Text);
                    if (!exit)
                    {
                        return exit;
                    }
                }
            }

            return exit;
        }

        /// <summary>
        /// Undoes this instance.
        /// </summary>
        public void Undo()
        {
            if (CanUndo)
            {
                ActiveTextEditor.Document.UndoStack.Undo();
                ActiveTextEditor.Refresh();
            }
        }

        /// <summary>
        /// Redoes this instance.
        /// </summary>
        public void Redo()
        {
            if (CanRedo)
            {
                ActiveTextEditor.Document.UndoStack.Redo();
                ActiveTextEditor.Refresh();
            }
        }

        /// <summary>
        /// Cuts the selected text.
        /// </summary>
        public void Cut()
        {
            if (ActiveTextEditorExists)
            {
                ActiveTextEditor.ActiveTextAreaControl.TextArea.ClipboardHandler.Cut(
                    this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Copies the selected text.
        /// </summary>
        public void Copy()
        {
			if (ActiveTextEditorExists)
            {
                ActiveTextEditor.ActiveTextAreaControl.TextArea.ClipboardHandler.Copy(
                    this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Pastes whatever text is on the clipboard.
        /// </summary>
        public void Paste()
        {
			if (ActiveTextEditorExists)
            {
                ActiveTextEditor.ActiveTextAreaControl.TextArea.ClipboardHandler.Paste(
                    this, EventArgs.Empty);
                Parse();
            }
        }

        /// <summary>
        /// Toggles the comment.
        /// </summary>
        public void ToggleComment()
        {
            ICSharpCode.TextEditor.Actions.ToggleComment toggleComment = new ICSharpCode.TextEditor.Actions.ToggleComment();
            toggleComment.Execute(ActiveTextEditor.ActiveTextAreaControl.TextArea);
        }

        /// <summary>
        /// Reloads the active editor.
        /// </summary>
        public void ReloadActiveEditor()
        {
			if (ActiveTextEditorExists)
            {
                ActiveTextEditor.Text = FileSystemService.GetDocumentText(documentContainer.ActiveDocument.Tag.ToString());
            }
        }

		/// <summary>
		/// Opens a new browser window if needed
		/// Refreshes the existing browser if it already exists
		/// In the rare case that .NET can't find the IE control, this function
		/// won't do anything at all, and the user will probably
		/// try to switch the Options back to use an external browser.
		/// </summary>
		public void OpenBrowser()
		{
			DockControl window = FindSpecificTab(Properties.Resources.BrowserTag);

            if (window == null)
            {
                window = CreateBrowserWindow();
            }

			if (window != null)
			{
				NavigateBrowser(window);
				documentContainer.ActiveDocument = window;
			}
		}

		private void NavigateBrowser(DockControl window)
		{
			EmbeddedBrowser browser = window.Controls[Properties.Resources.BrowerControlName] as EmbeddedBrowser;

			if (browser != null)
			{
				browser.Visible = true;
				string url = string.Format("{0}:{1}/", Properties.Resources.BrowserInitialUrl, Options.Instance.ServerPort.ToString());
				browser.Navigate(url);
			}
		}

		private DockControl CreateBrowserWindow()
		{
			try
			{
				DockControl window = CreateDockWindow(Properties.Resources.BrowserTitle, Properties.Resources.BrowserTag);
				window.Closable = false;
				EmbeddedBrowser browser = new EmbeddedBrowser();
				browser.Name = Properties.Resources.BrowerControlName;
				browser.Dock = DockStyle.Fill;
				browser.TopLevel = false;
				browser.Size = new Size(window.Width, window.Height);
				window.Controls.Add(browser);
				documentContainer.AddDocument(window);

				return window;
			}

			catch
			{
			}

			return null;
		}

        /// <summary>
        /// Opens the document.
        /// </summary>
        /// <param name="e">The <see cref="T:RideMe.Core.ProjectFileEventArgs"/> instance containing the event data.</param>
        private void OpenDocument(ProjectFileEventArgs e)
        {
            if (string.IsNullOrEmpty(e.FileName )) return;

            DockControl window = CreateDockWindow(e.FileName, e.FilePath);
            int topOffset = 0;
            bool isRubyFile = FileSystemService.IsRubyFile(e.FileName);
            if (isRubyFile)
            {
                ClassMethodDropDowns classMethodDropDowns = CreateClassMethodDropDownControl();
                window.Controls.Add(classMethodDropDowns);
                topOffset = classMethodDropDowns.Height;
            }
            TextEditorControl textEditor = CreateTextEditor(window, e.FilePath, topOffset);
            window.Controls.Add(textEditor);
            textEditor.FileName = e.FilePath;
            if(FileSystemService.IsReadOnlyFile(e.FilePath))
            {
                textEditor.IsReadOnly = true;
                window.Text += Resources.ReadOnlyIndicatorMessage;
            }

            // Add this to the singleton object so other classes can get at it.
            ActiveDocumentInformation.Instance.ActiveTextEditor = textEditor;
            ActiveDocumentInformation.Instance.OpenTextEditors.Add(textEditor);
            try
            {
                textEditor.Focus();
                documentContainer.AddDocument(window);
                documentContainer.ActiveDocument = window;
                _loadingDocument = true;
                textEditor.Text = FileSystemService.GetDocumentText(e.FilePath);
                textEditor.Document.FoldingManager.FoldingStrategy = new RubyFoldingStrategy();
                if (isRubyFile)
                {
                    RubyParser.Parse(textEditor.Document.TextContent);
                    Parse();
                }

                if ((e.Offset != -1) && (e.Length != -1))
                {
                    PositionCaret(e.Offset, textEditor);
                }
                textEditor.Focus();
                
            }
            catch (IOException)
            {
                MessageService.ShowError(this, Resources.CantOpenFileMessage);
                DisposeDocumentAndEditor(window);
            }
        }

        private void PositionCaret(int offset, TextEditorControl textEditor)
        {
            Point startPosition = textEditor.Document.OffsetToPosition(offset);
            textEditor.ActiveTextAreaControl.Caret.Position = startPosition;
        }

        /// <summary>
        /// Creates the dock window.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="filePath">The file path.</param>
        /// <returns></returns>
        private DockControl CreateDockWindow(string fileName, string filePath)
        {
            DockControl window = new DockControl();

            this.SuspendLayout();

            window.Size = this.Size;
            window.Closing += new CancelEventHandler(window_Closing);
            window.Text = fileName;
            window.Tag = filePath;
           
            window.BackColor = GetTabBackColor(window, filePath);
            window.KeyDown += new System.Windows.Forms.KeyEventHandler( TextArea_KeyDown );

            this.ResumeLayout();

			window.AllowDrop = true;
			window.DragOver += new DragEventHandler(window_DragOver);
            return window;
        }

		void window_DragOver(object sender, DragEventArgs e)
		{
			
		}

        private Color GetTabBackColor(DockControl window, string filePath)
        {
            DocumentTypes documentType = FileSystemService.GetDocumentType(filePath);

            switch (documentType)
            {
                case DocumentTypes.Model :
                    return Options.Instance.ModelColor;
                case DocumentTypes.View :
                    return Options.Instance.ViewColor;
                case DocumentTypes.Controller :
                    return Options.Instance.ControllerColor;
                case DocumentTypes.Tests :
                    return Options.Instance.TestsColor;
                default :
                    return SystemColors.Control;
            }
        }

        /// <summary>
        /// Creates a text editor control.
        /// </summary>
        /// <param name="window">The window.</param>
        /// <param name="filePath">The file path.</param>
        /// <returns></returns>
        private TextEditorControl CreateTextEditor(DockControl window, string filePath, int offsetTop)
        {
            TextEditorControl textEditor = new TextEditorControl();
            textEditor.Name = TextEditorControlName;

            // Create a border.
            textEditor.TabIndex = 0;
            textEditor.Width = window.Width - WindowBorderOffset;
            textEditor.Height = window.Height - WindowBorderOffset - offsetTop;
            textEditor.Left = (window.Width - textEditor.Width + WindowBorderOffset) / 2;
            textEditor.Top = (window.Height - textEditor.Height + WindowBorderOffset + offsetTop) / 2;
            textEditor.Anchor = AnchorStyles.Top | AnchorStyles.Bottom |
                AnchorStyles.Left | AnchorStyles.Right;
            textEditor.BorderStyle = BorderStyle.Fixed3D;
            textEditor.TextEditorProperties.ShowSpaces = Options.Instance.ShowSpaces;
            textEditor.TextEditorProperties.ShowTabs = Options.Instance.ShowTabs;
            textEditor.TextEditorProperties.ShowInvalidLines = false;
            textEditor.TextEditorProperties.ShowHorizontalRuler = false;
            textEditor.TextEditorProperties.ShowEOLMarker = Options.Instance.ShowEOL;
            textEditor.TextEditorProperties.AutoInsertCurlyBracket = false;
            textEditor.TextEditorProperties.ShowVerticalRuler = false;
            textEditor.TextEditorProperties.CutCopyWholeLine = true;
            textEditor.TextEditorProperties.TabIndent = 2;
			textEditor.TextEditorProperties.ConvertTabsToSpaces = Options.Instance.ConvertTabsToSpaces;
            textEditor.TextEditorProperties.EnableFolding = Options.Instance.EnableFolding;

            textEditor.TextEditorProperties.LineTerminator = FileSystemService.GetLineEndingCharacter(
                Options.Instance.LineEndings);
            textEditor.ShowLineNumbers = Options.Instance.ShowLineNumbers;
            textEditor.TextEditorProperties.Font = Options.Instance.EditorFont;
            textEditor.BackColor = SystemColors.Control;
            textEditor.ContextMenuStrip = this.ContextMenuStrip;
            textEditor.ActiveTextAreaControl.Caret.PositionChanged += new EventHandler(Caret_PositionChanged);
            textEditor.ActiveTextAreaControl.TextArea.KeyDown += new System.Windows.Forms.KeyEventHandler( TextArea_KeyDown );
            textEditor.ActiveTextAreaControl.TextArea.KeyUp += new System.Windows.Forms.KeyEventHandler(TextArea_KeyUp);
            textEditor.Disposed += new EventHandler(textEditor_Disposed);
            textEditor.Document.HighlightingStrategy =  HighlightingManager.Manager.FindHighlighterForFile(filePath);

            textEditor.Document.DocumentChanged += new DocumentEventHandler(Document_DocumentChanged);

            if (_contextMenu != null)
            {
                textEditor.ContextMenuStrip = _contextMenu;
            }

			textEditor.AllowDrop = true;
			textEditor.DragDrop += new DragEventHandler(textEditor_DragDrop);
            return textEditor;
        }

		void textEditor_DragDrop(object sender, DragEventArgs e)
		{
			string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

			foreach (string file in files)
			{
				ProjectService.AddOpenFile(file);
			}
		}


        private ClassMethodDropDowns CreateClassMethodDropDownControl()
        {
            ClassMethodDropDowns classMethodDropDowns = new ClassMethodDropDowns();
            classMethodDropDowns.Name = ClassMethodDropDownControlName;
            classMethodDropDowns.BackColor = SystemColors.Control;
            classMethodDropDowns.Dock = DockStyle.Top;
            classMethodDropDowns.TabStop = false;
            classMethodDropDowns.SelectedTopLevelEntityChanged += new EventHandler(classMethodDropDowns_SelectedTopLevelEntityChanged);
            classMethodDropDowns.SelectedMethodChanged += new EventHandler(classMethodDropDowns_SelectedMethodChanged);
            return classMethodDropDowns;

        }

        /// <summary>
        /// Updates the caret position.
        /// </summary>
        private void UpdateCaretPosition()
        {

			if (ActiveTextEditorExists)
            {

                // Call this so the event fires, and the other presentation elements 
                // can display the line, column numbers.
                ProjectService.UpdateCaretPosition(ActiveTextEditor.ActiveTextAreaControl.Caret.Line, ActiveTextEditor.ActiveTextAreaControl.Caret.Column);
            }
        }

        /// <summary>
        /// Finds the specific tab.
        /// </summary>
        /// <param name="file">The file.</param>
        /// <returns></returns>
        private DockControl FindSpecificTab(string file)
        {
            foreach (DockControl control in documentContainer.Controls)
            {
                if (control.Tag.ToString() == file)
                {
                    return control;
                }
            }

            return null;
        }

        /// <summary>
        /// Asks to save changes.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="contents">The contents.</param>
        /// <returns></returns>
        private bool AskToSaveChanges(string path, string contents)
        {
            DialogResult messageResult = 
                MessageService.ShowQuestionWithCancel(this, string.Format(Resources.SaveDirtyFileMessage, path));

            if (messageResult  == DialogResult.Cancel)
            {
                return false;
            }
            else if (messageResult == DialogResult.Yes)
            {
                SaveCurrentDocument(path, contents);
            }
            return true;
        }

        /// <summary>
        /// Saves the current document.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="contents">The contents.</param>
        private void SaveCurrentDocument(string path, string contents)
        {
            try
            {
                FileSystemService.SaveDocument(path, contents);

                RemoveAsterisk(documentContainer.ActiveDocument);
            }
            catch (UnauthorizedAccessException)
            {
                MessageService.ShowError(null, Resources.FileIsReadOnlyMessage);
            }
        }

        private void RemoveAsterisk(DockControl tab)
        {
            if (tab.Text.Contains(Resources.Asterisk))
            {
                tab.Text = documentContainer.ActiveDocument.Text.Substring(
                    0, documentContainer.ActiveDocument.Text.Length - 1);
            }
        }

        /// <summary>
        /// Disposes the document and editor.
        /// </summary>
        /// <param name="dockToDispose">The dock to dispose.</param>
        private void DisposeDocumentAndEditor(DockControl dockToDispose)
        {
            // I knew the TextEditor control uses unmanaged resources,
            // so ensure they are freed like a good like considerate coder.
            foreach (Control control in dockToDispose.Controls)
            {
                control.Dispose();
            }

            // Close is setup to call dispose. 
            dockToDispose.Close();
        }

        /// <summary>
        /// Renames the file in tab.
        /// </summary>
        /// <param name="oldFilePath">The old file path.</param>
        /// <param name="newFileName">New name of the file.</param>
        /// <param name="newFilePath">The new file path.</param>
        private void RenameFileInTab(string oldFilePath, string newFileName, string newFilePath)
        {
            DockControl foundTab = FindSpecificTab(oldFilePath);

            if (foundTab != null)
            {
                foundTab.Text = newFileName;
                foundTab.Tag = newFilePath;
            }
        }

        /// <summary>
        /// Replaces the shortcut with a snippet if one exits.
        /// </summary>
        /// <param name="activeTextArea">The active text area.</param>
        /// <param name="line">The line.</param>
        private static void ReplaceShortcutWithSnippet(TextArea activeTextArea, LineSegment line)
        {
            try
            {
                // There has to be at least two words counting the tab.
                if (line.Words.Count == 1) return;

                // Step backwards through the line finding the last word.
                int i = activeTextArea.Caret.Column - 1;
			    string word = null;
			    int whitespace = 0;
			    while ((i > -1) && (word == null))
			    {
                    TextWord textWord = line.GetWord(i);
                    if (!textWord.IsWhiteSpace)
                    {
                        word = textWord.Word;
                        break;
                    }
                    else
                    {
                    	whitespace +=  1;
                    }
                    i--;
                }

                string snippet = SnippetService.FindSnippet(word);

                if (snippet.Length > 0)
                {
                    int offset = activeTextArea.Caret.Offset - word.Length - whitespace;

                    if (offset < 0)
                    {
                        offset = 0;
                    }

                    // Remove the shortcut and the tab (that's what the +1 is for).
                    activeTextArea.Document.Remove(offset, word.Length);
                    activeTextArea.Document.Insert(offset, snippet);

                    // Now position the caret at the end of the snippet.
                    activeTextArea.Caret.Column = offset + snippet.Length;
                }
            }
            catch (IOException)
            {
                MessageService.ShowError(null, Resources.CantFindSnippetFileMessage);
            }
        }

        public void CloseBrowser()
        {
            DockControl window = FindSpecificTab(Properties.Resources.BrowserTag);
            
            if (window != null)
            {
                window.Close();
            }
        }

        private ClassMethodDropDowns GetClassMethodDropDowns()
        {
            return  documentContainer.ActiveDocument.Controls[ClassMethodDropDownControlName] as ClassMethodDropDowns;
        }

        private void UpdateFoldingsAndDropDowns()
        {
			if (GetClassMethodDropDowns() != null && ActiveTextEditorExists)
            {
                GetClassMethodDropDowns().LoadDropDowns(RubyParser.TopLevelEntities);

                ActiveTextEditor.Document.FoldingManager.UpdateFoldings(null, RubyParser.TopLevelEntities);
            }
        }

        private void Parse()
        {
            RubyParser.Buffer = ActiveTextEditor.Document.TextContent;
            RubyParser.Parse();
        }

        private string ConvertLineSegmentToString(LineSegment line)
        {
            string tempLine = string.Empty;

            if (line.Words != null)
            {
                foreach (TextWord word in line.Words)
                {
                    tempLine += word.Word;
                }
            }

            return tempLine;
        }

        /// <summary>
        /// Handles the FileRenamed event of the ProjectService control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:RideMe.Core.FileRenameEventArgs"/> instance containing the event data.</param>
        private void ProjectService_FileRenamed(object sender, FileRenameEventArgs e)
        {
            RenameFileInTab(e.OldFilePath, e.NewFileName, e.NewFilePath);
        }

        /// <summary>
        /// Handles the FileDeleted event of the ProjectService control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:RideMe.Core.ProjectFileEventArgs"/> instance containing the event data.</param>
        private void ProjectService_FileDeleted(object sender, ProjectFileEventArgs e)
        {
            DockControl foundTab = FindSpecificTab(e.FilePath);

            if (foundTab != null)
            {
                DisposeDocumentAndEditor(foundTab);
            }
        }

        /// <summary>
        /// Handles the ProjectClosed event of the ProjectService control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void ProjectService_ProjectClosed(object sender, EventArgs e)
        {
            if (InvokeRequired)
            {
                Invoke(new MethodInvoker(
                    delegate
                    {
                        ProjectService_ProjectClosed(sender, e);
                    }));
            }
            else
            {
                while (documentContainer.ActiveDocument != null)
                {
					
                    documentContainer.ActiveDocument.Close();
                }
            }
        }

        /// <summary>
        /// Handles the FileOpened event of the ProjectService control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:RideMe.Core.ProjectFileEventArgs"/> instance containing the event data.</param>
        private void ProjectService_FileOpened(object sender, ProjectFileEventArgs e)
        {
            OpenDocument(e);

			if (ActiveTextEditorExists)
            {
                // If I don't do this (I had to write that method btw), 
                // the caret is hidden.
                ActiveTextEditor.ActiveTextAreaControl.Caret.ShowCaret();
            }
        }

        /// <summary>
        /// Handles the SaveClicked event of the ProjectService control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void ProjectService_SaveClicked(object sender, EventArgs e)
        {
            SaveCurrentDocument(documentContainer.ActiveDocument.Tag.ToString(), ActiveTextEditor.Text);

            Cursor.Current = Cursors.Default;
        }

        /// <summary>
        /// Handles the SaveAllClicked event of the ProjectService control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void ProjectService_SaveAllClicked(object sender, EventArgs e)
        {
            foreach (DockControl control in documentContainer.Controls)
            {
                FileSystemService.SaveDocument(control.Tag.ToString(), control.Controls[TextEditorControlName].Text);
                RemoveAsterisk(control);
            }

            Cursor.Current = Cursors.Default;
        }

        /// <summary>
        /// Handles the SaveAsClicked event of the ProjectService control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:RideMe.Core.FileRenameEventArgs"/> instance containing the event data.</param>
        private void ProjectService_SaveAsClicked(object sender, FileRenameEventArgs e)
        {
            FileSystemService.SaveDocument(e.NewFilePath, ActiveTextEditor.Text);

            RenameFileInTab(e.OldFilePath, e.NewFileName, e.NewFilePath);

            RemoveAsterisk(documentContainer.ActiveDocument);

        }

        /// <summary>
        /// Handles the OpenFileSelected event of the ProjectService control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:RideMe.Core.ProjectFileEventArgs"/> instance containing the event data.</param>
        private void ProjectService_OpenFileSelected(object sender, ProjectFileEventArgs e)
        {
            DockControl tabInQuestion = FindSpecificTab(e.FilePath);

            if (tabInQuestion != null)
            {
                documentContainer.ActiveDocument = tabInQuestion;
            }

            if ((e.Offset != -1) && (e.Length != -1))
            {
                PositionCaret(e.Offset, documentContainer.ActiveDocument.Controls[TextEditorControlName] as TextEditorControl);
            }

			if (ActiveTextEditorExists)
            {
                // If I don't do this (I had to write that method btw), 
                // the caret is hidden.
                ActiveTextEditor.ActiveTextAreaControl.Caret.ShowCaret();
            }

        }

        /// <summary>
        /// Handles the PreparedForServer event of the ProjectService control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:RideMe.Core.ProjectFileEventArgs"/> instance containing the event data.</param>
        private void ProjectService_PreparedForServer(object sender, ProjectFileEventArgs e)
        {
            // If dispatch.fcgi is open, when want to reload it.
            DockControl tabToFind = FindSpecificTab(e.FilePath);

            if (tabToFind != null)
            {
                documentContainer.ActiveDocument = tabToFind;
                ReloadActiveEditor();
            }
        }

        /// <summary>
        /// Handles the ActiveTabbedDocumentChanged event of the _dockManager control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void _dockManager_ActiveTabbedDocumentChanged(object sender, EventArgs e)
        {
			if (ActiveTextEditorExists)
            {
                UpdateCaretPosition();
            }
        }

        /// <summary>
        /// Handles the PositionChanged event of the Caret control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void Caret_PositionChanged(object sender, EventArgs e)
        {
            UpdateCaretPosition();
        }

        /// <summary>
        /// Handles the KeyDown event of the control control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.Windows.Forms.KeyEventArgs"/> instance containing the event data.</param>
        private void TextArea_KeyDown( object sender, KeyEventArgs e )
        {

            // moved from control_KeyDown by Jason H.
            //
            if (((e.KeyCode == Keys.F4) && (e.Modifiers == Keys.Control)) || ((e.Modifiers == Keys.Control) && (e.KeyCode == Keys.W)))
            {
                // changed to use CloseWindows by Jason H.
                // documentContainer.ActiveDocument.Close();
                CloseWindow();
            }

        }

        /// <summary>
        /// Handles the OptionsChanged event of the options control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void options_OptionsChanged(object sender, EventArgs e)
        {
            foreach (DockControl window in documentContainer.Controls)
            {
                TextEditorControl currentTextEditor = window.Controls[TextEditorControlName] as TextEditorControl;
                if (currentTextEditor != null)
                {
                    currentTextEditor.ShowLineNumbers = Options.Instance.ShowLineNumbers;
                    currentTextEditor.Font = Options.Instance.EditorFont;
					currentTextEditor.ConvertTabsToSpaces = Options.Instance.ConvertTabsToSpaces;
                    currentTextEditor.EnableFolding = Options.Instance.EnableFolding;
                    if (Options.Instance.EnableFolding)
                    {
                        foreach (DockControl control in documentContainer.Controls)
                        {
                            if (!FileSystemService.IsRubyFile(control.Tag.ToString())) break;

                            TextEditorControl editor = control.Controls[TextEditorControlName] as TextEditorControl;

                            if (editor == null) break;

                            // We only want to parse and update foldings if they didn't
                            // have folding on previously.
                            if (editor.Document.FoldingManager.FoldMarker.Count == 0)
                            {
                                RubyParser.Parse(editor.Text);
                                UpdateFoldingsAndDropDowns();
                            }
                        }
                    }

                    foreach (DockControl control in documentContainer.Controls)
                    {
                        control.BackColor = GetTabBackColor(control, control.Text);
                    }
                }
            }

			HighlightingManager.Manager.ReloadSyntaxModes();
        }

        /// <summary>
        /// Handles the Disposed event of the textEditor control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void textEditor_Disposed(object sender, EventArgs e)
        {
            TextEditorControl castedSender = sender as TextEditorControl;

            if (castedSender != null)
            {
                castedSender.ActiveTextAreaControl.Caret.PositionChanged -= new EventHandler(Caret_PositionChanged);
                castedSender.ActiveTextAreaControl.TextArea.KeyDown -= new System.Windows.Forms.KeyEventHandler( TextArea_KeyDown );
                castedSender.Disposed -= new EventHandler(textEditor_Disposed);
            }
        }

        /// <summary>
        /// Handles the Closing event of the window control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param>
        private void window_Closing(object sender, CancelEventArgs e)
        {
			// See if the current doc is dirty.
            if (ActiveEditorDirty)
            {
                // If so, ask if the want to save changes. If it returns false, that means cancel.
                if (!AskToSaveChanges(documentContainer.ActiveDocument.Tag.ToString(), ActiveTextEditor.Text))
                {
                    e.Cancel = true;
                }
            }

            // If all documents are closed tell the project service so
            // various screen elements can be updated.
            if ((documentContainer.HasChildren) & (!e.Cancel))
            {
                ProjectService.UpdateLastDocumentClosed();
            }

            DockControl activeTab = sender as DockControl;

            string filePath = activeTab.Tag.ToString(); //documentContainer.ActiveDocument.Tag.ToString();
            ProjectService.CloseFile(filePath);
            ActiveDocumentInformation.Instance.OpenTextEditors.Remove(activeTab.Controls[ClassMethodDropDownControlName] as TextEditorControl);

        }

        /// <summary>
        /// Handles the ActiveDocumentChanged event of the documentContainer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:TD.SandDock.ActiveDocumentEventArgs"/> instance containing the event data.</param>
        private void documentContainer_ActiveDocumentChanged(object sender, ActiveDocumentEventArgs e)
        {
            if (documentContainer.Documents.Length != 1)
            {
                UpdateCaretPosition();
                if (e.NewActiveDocument != null)
                {
                    ProjectService.ChangeTab(e.NewActiveDocument.Tag.ToString());
                }

                if (documentContainer.Documents.Length > 0)
                {
                    // If this isn't true, it must be the browser tab.
                    if (e.NewActiveDocument.Controls.ContainsKey(TextEditorControlName))
                    {
                        ActiveDocumentInformation.Instance.ActiveTextEditor = e.NewActiveDocument.Controls[TextEditorControlName] as TextEditorControl;
                    }
                }
            }
        }

        /// <summary>
        /// Handles the KeyUp event of the TextArea control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.Windows.Forms.KeyEventArgs"/> instance containing the event data.</param>
        private void TextArea_KeyUp(object sender, KeyEventArgs e)
        {

            if (e.KeyData == Keys.Tab)
            {
                TextArea castedSender = sender as TextArea;

                if (castedSender != null)
                {
                    LineSegment line = castedSender.Document.GetLineSegment(castedSender.Caret.Line);

                    if ((line.Words == null) || (line.Words.Count == 0)) return;

                    ReplaceShortcutWithSnippet(castedSender, line);
                }
            }

            if (e.KeyCode ==  Keys.V && e.Modifiers == Keys.Control)
            {
                Parse();
            }

        }

        private void Instance_TabSwitched(object sender, SwitchTabEventArgs e)
        {
            foreach (DockControl document in this.documentContainer.Documents)
            {
				if (document.Controls[TextEditorControlName] == e.DesiredTextEditor)
                {
                    this.documentContainer.ActiveDocument = document;
                }
            }
        }

        private void Document_DocumentChanged(object sender, DocumentEventArgs e)
        {
            if (this.documentContainer.ActiveDocument == null) return;

            if (!this.documentContainer.ActiveDocument.Text.Contains(Resources.Asterisk))
            {
                if (_loadingDocument)
                {
                    _loadingDocument = false;
                    return;
                }

                this.documentContainer.ActiveDocument.Text =
                        this.documentContainer.ActiveDocument.Text.Insert(
                            this.documentContainer.ActiveDocument.Text.Length, Resources.Asterisk);
            }

            if ((RubyFileActive) && (RubyParser.IsEndStatement(ConvertLineSegmentToString(e.Document.GetLineSegment(e.Document.GetLineNumberForOffset(e.Offset))))))
            {
                RubyParser.Parse(e.Document.TextContent);
            }

        }

        private void classMethodDropDowns_SelectedTopLevelEntityChanged(object sender, EventArgs e)
        {
            // This is ugly, but I have to set the caret to the end of the method, then
            // back up to that start in order for the whole method to be displayed. [shrugs]

            if (((ClassMethodDropDowns)sender).SelectedTopLevelEntity.End != null)
            {
                ActiveTextEditor.ActiveTextAreaControl.Caret.Line = ((ClassMethodDropDowns)sender).SelectedTopLevelEntity.End.Line;
                ActiveTextEditor.Focus();
                ActiveTextEditor.ActiveTextAreaControl.Caret.Line = ((ClassMethodDropDowns)sender).SelectedTopLevelEntity.Line;
            }
        }

        private void classMethodDropDowns_SelectedMethodChanged(object sender, EventArgs e)
        {
            // This is ugly, but I have to set the caret to the end of the method, then
            // back up to that start in order for the whole method to be displayed. [shrugs]

            ActiveTextEditor.ActiveTextAreaControl.Caret.Line = ((ClassMethodDropDowns)sender).SelectedMethod.End.Line;
            ActiveTextEditor.Focus();
            ActiveTextEditor.ActiveTextAreaControl.Caret.Line = ((ClassMethodDropDowns)sender).SelectedMethod.Line;

        }

        private void RubyParser_ParseComplete(object sender, EventArgs e)
        {
            UpdateFoldingsAndDropDowns();
        }

        public string GetCurrentSelectionOrNearestWord()
        {
            SelectionManager currentSelectionManager = ActiveTextEditor.ActiveTextAreaControl.SelectionManager;
            string defaultFindWhatText = string.Empty;

            // If nothing is selected, try to find the nearest word.
            if (!currentSelectionManager.HasSomethingSelected)
            {
                TextArea currentTextArea = ActiveTextEditor.ActiveTextAreaControl.TextArea;
                Point currentPosition = currentTextArea.Caret.Position;

                new WordLeft().Execute(currentTextArea);
                new ShiftWordRight().Execute(currentTextArea);
                defaultFindWhatText = currentSelectionManager.SelectedText.Trim();

                currentSelectionManager.ClearSelection();
                currentTextArea.Caret.Position = currentPosition;
            }
            else
            {
                defaultFindWhatText = currentSelectionManager.SelectedText;
            }

            return defaultFindWhatText;

        }

        public void Print()
        {
            if (printDialog1.ShowDialog() == DialogResult.OK)
            {
                ActiveTextEditor.PrintDocument.DocumentName = documentContainer.ActiveDocument.Tag.ToString();
                ActiveTextEditor.PrintDocument.PrinterSettings = printDialog1.PrinterSettings;
                ActiveTextEditor.PrintDocument.Print();
            }
            
        }

        public void ConvertTabsToSpaces()
        {
            int length = ActiveTextEditor.Document.TextLength;

            string what = ActiveTextEditor.Document.GetText(0, length);
            string spaces = new string(' ', ActiveTextEditor.Document.TextEditorProperties.TabIndent);
            ActiveTextEditor.Document.Replace(0, length, what.Replace("\t", spaces));
            
        }

    }
}
